package com.tiennt.android.apkextractor.loader;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.support.v4.content.AsyncTaskLoader;
import android.util.Log;

import com.tiennt.android.apkextractor.model.AppEntry;
import com.tiennt.android.apkextractor.observer.InstalledAppsObserver;
import com.tiennt.android.apkextractor.observer.SystemLocaleObserver;
import com.tiennt.android.apkextractor.utilities.Config;

public class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {
	public final Context mContext;
	public final PackageManager mPm;
	private List<AppEntry> mApps;
	private int mAppType;

	public AppListLoader(Context context, int loaderForAppType) {
		super(context);
		mAppType = loaderForAppType;
		mContext = context;
		mPm = getContext().getPackageManager();
	}

	@Override
	public List<AppEntry> loadInBackground() {
		// TODO Auto-generated method stub
		if (Config.DEBUG) {
			Log.v(Config.TAG,
					" ++++++++++++++++ Start load apps ++++++++++++++++++ ");
		}

		List<ApplicationInfo> appList = mPm.getInstalledApplications(0);

		List<AppEntry> result = new ArrayList<AppEntry>(appList.size());

		if (mAppType == AppType.SYSTEM) {
			for (ApplicationInfo info : appList) {
				if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 1) {
					AppEntry entry = new AppEntry(this, info);
					entry.loadLabel(mContext);
					result.add(entry);
				}
			}
		} else if (mAppType == AppType.USER) {
			for (ApplicationInfo info : appList) {
				if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 1) {
					AppEntry entry = new AppEntry(this, info);
					entry.loadLabel(mContext);
					result.add(entry);
				}
			}
		}

		Collections.sort(result, ALPHA_COMPARATOR);

		return result;
	}

	@Override
	public void deliverResult(List<AppEntry> data) {
		// TODO Auto-generated method stub
		if (isReset()) {
			if (Config.DEBUG) {
				Log.v(Config.TAG,
						" +++++++++++++++++++ REset ++++++++++++++++++ ");
			}

			if (data != null) {
				releaseResources(data);
			}
		}

		List<AppEntry> oldApps = mApps;
		mApps = data;

		if (isStarted()) {
			super.deliverResult(mApps);
		}

		if (oldApps != null && oldApps != data) {
			releaseResources(oldApps);
		}
	}

	@Override
	protected void onStartLoading() {
		if (Config.DEBUG)
			Log.i(Config.TAG, "+++ onStartLoading() called! +++");

		if (mApps != null) {
			// Deliver any previously loaded data immediately.
			if (Config.DEBUG)
				Log.i(Config.TAG,
						"+++ Delivering previously loaded data to the client...");
			deliverResult(mApps);
		}

		// Register the observers that will notify the Loader when changes are
		// made.
		if (mAppsObserver == null) {
			mAppsObserver = new InstalledAppsObserver(this);
		}

		if (mLocaleObserver == null) {
			mLocaleObserver = new SystemLocaleObserver(this);
		}

		if (takeContentChanged()) {
			// When the observer detects a new installed application, it will
			// call
			// onContentChanged() on the Loader, which will cause the next call
			// to
			// takeContentChanged() to return true. If this is ever the case (or
			// if
			// the current data is null), we force a new load.
			if (Config.DEBUG)
				Log.i(Config.TAG,
						"+++ A content change has been detected... so force load! +++");
			forceLoad();
		} else if (mApps == null) {
			// If the current data is null... then we should make it non-null!
			// :)
			if (Config.DEBUG)
				Log.i(Config.TAG,
						"+++ The current data is data is null... so force load! +++");
			forceLoad();
		}
	}

	@Override
	protected void onStopLoading() {
		if (Config.DEBUG)
			Log.i(Config.TAG, "+++ onStopLoading() called! +++");

		// The Loader has been put in a stopped state, so we should attempt to
		// cancel the current load (if there is one).
		cancelLoad();

		// Note that we leave the observer as is; Loaders in a stopped state
		// should still monitor the data source for changes so that the Loader
		// will know to force a new load if it is ever started again.
	}

	@Override
	protected void onReset() {
		if (Config.DEBUG)
			Log.i(Config.TAG, "+++ onReset() called! +++");

		// Ensure the loader is stopped.
		onStopLoading();

		// At this point we can release the resources associated with 'apps'.
		if (mApps != null) {
			releaseResources(mApps);
			mApps = null;
		}

		// The Loader is being reset, so we should stop monitoring for changes.
		if (mAppsObserver != null) {
			getContext().unregisterReceiver(mAppsObserver);
			mAppsObserver = null;
		}

		if (mLocaleObserver != null) {
			getContext().unregisterReceiver(mLocaleObserver);
			mLocaleObserver = null;
		}
	}

	@Override
	public void onCanceled(List<AppEntry> apps) {
		if (Config.DEBUG)
			Log.i(Config.TAG, "+++ onCanceled() called! +++");

		// Attempt to cancel the current asynchronous load.
		super.onCanceled(apps);

		// The load has been canceled, so we should release the resources
		// associated with 'mApps'.
		releaseResources(apps);
	}

	@Override
	public void forceLoad() {
		if (Config.DEBUG)
			Log.i(Config.TAG, "+++ forceLoad() called! +++");
		super.forceLoad();
	}

	/**
	 * Helper method to take care of releasing resources associated with an
	 * actively loaded data set.
	 */
	private void releaseResources(List<AppEntry> apps) {
		// For a simple List, there is nothing to do. For something like a
		// Cursor,
		// we would close it in this method. All resources associated with the
		// Loader should be released here.
	}

	/*********************************************************************/
	/** (4) Observer which receives notifications when the data changes **/
	/*********************************************************************/

	// An observer to notify the Loader when new apps are installed/updated.
	private InstalledAppsObserver mAppsObserver;

	// The observer to notify the Loader when the system Locale has been
	// changed.
	private SystemLocaleObserver mLocaleObserver;

	private static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
		Collator sCollator = Collator.getInstance();

		@Override
		public int compare(AppEntry object1, AppEntry object2) {
			return sCollator.compare(object1.getLabel(), object2.getLabel());
		}
	};

	public static class AppType {
		public static final int USER = 1;
		public static final int SYSTEM = 2;
	}
}
